package oceanlife.pathfinding;

/*
 * Copyright (c) 2005, 2007 by L. Paul Chew.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;

import javax.swing.*;

/**
 * The Delauany applet.
 *
 * Creates and displays a Delaunay Triangulation (DT) or a Voronoi Diagram
 * (VoD). Has a main program so it is an application as well as an applet.
 *
 * @author Paul Chew
 *
 * Created July 2005. Derived from an earlier, messier version.
 *
 * Modified December 2007. Updated some of the Triangulation methods. Added the
 * "Colorful" checkbox. Reorganized the interface between DelaunayAp and
 * DelaunayPanel. Added code to find a Voronoi cell.
 *
 */
@SuppressWarnings("serial")
public class DelaunayAp extends javax.swing.JApplet
        implements Runnable, ActionListener, MouseListener {

    private boolean debug = false;             // Used for debugging
    private Component currentSwitch = null;    // Entry-switch that mouse is in

    private static String windowTitle = "Voronoi/Delaunay Window";
    private JRadioButton voronoiButton = new JRadioButton("Voronoi Diagram");
    private JRadioButton delaunayButton =
                                    new JRadioButton("Delaunay Triangulation");
    private JButton clearButton = new JButton("Clear");
    private JCheckBox colorfulBox = new JCheckBox("More Colorful");
    private DelaunayPanel delaunayPanel = new DelaunayPanel(this);
    private JLabel circleSwitch = new JLabel("Show Empty Circles");
    private JLabel delaunaySwitch = new JLabel("Show Delaunay Edges");
    private JLabel voronoiSwitch = new JLabel("Show Voronoi Edges");

    /**
     * Main program (used when run as application instead of applet).
     */
    public static void main (String[] args) {
        DelaunayAp applet = new DelaunayAp();    // Create applet
        applet.init();                           // Applet initialization
        JFrame dWindow = new JFrame();           // Create window
        dWindow.setSize(700, 500);               // Set window size
        dWindow.setTitle(windowTitle);           // Set window title
        dWindow.setLayout(new BorderLayout());   // Specify layout manager
        dWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                                                 // Specify closing behavior
        dWindow.add(applet, "Center");           // Place applet into window
        dWindow.setVisible(true);                // Show the window
    }

    /**
     * Initialize the applet.
     * As recommended, the actual use of Swing components takes place in the
     * event-dispatching thread.
     */
    public void init () {
        try {SwingUtilities.invokeAndWait(this);}
        catch (Exception e) {System.err.println("Initialization failure");}
    }

    /**
     * Set up the applet's GUI.
     * As recommended, the init method executes this in the event-dispatching
     * thread.
     */
    public void run () {
        setLayout(new BorderLayout());

        // Add the button controls
        ButtonGroup group = new ButtonGroup();
        group.add(voronoiButton);
        group.add(delaunayButton);
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(voronoiButton);
        buttonPanel.add(delaunayButton);
        buttonPanel.add(clearButton);
        buttonPanel.add(new JLabel("          "));      // Spacing
        buttonPanel.add(colorfulBox);
        this.add(buttonPanel, "North");

        // Add the mouse-entry switches
        JPanel switchPanel = new JPanel();
        switchPanel.add(circleSwitch);
        switchPanel.add(new Label("     "));            // Spacing
        switchPanel.add(delaunaySwitch);
        switchPanel.add(new Label("     "));            // Spacing
        switchPanel.add(voronoiSwitch);
        this.add(switchPanel, "South");

        // Build the delaunay panel
        delaunayPanel.setBackground(Color.gray);
        this.add(delaunayPanel, "Center");

        // Register the listeners
        voronoiButton.addActionListener(this);
        delaunayButton.addActionListener(this);
        clearButton.addActionListener(this);
        colorfulBox.addActionListener(this);
        delaunayPanel.addMouseListener(this);
        circleSwitch.addMouseListener(this);
        delaunaySwitch.addMouseListener(this);
        voronoiSwitch.addMouseListener(this);

        // Initialize the radio buttons
        voronoiButton.doClick();
        colorfulBox.doClick();
        

    }

    /**
     * A button has been pressed; redraw the picture.
     */
    public void actionPerformed(ActionEvent e) {
        if (debug)
            System.out.println(((AbstractButton)e.getSource()).getText());
        if (e.getSource() == clearButton) delaunayPanel.gB.trans.clear();
        delaunayPanel.repaint();
    }

    /**
     * If entering a mouse-entry switch then redraw the picture.
     */
    public void mouseEntered(MouseEvent e) {
        currentSwitch = e.getComponent();
        if (currentSwitch instanceof JLabel) delaunayPanel.repaint();
        else currentSwitch = null;
    }

    /**
     * If exiting a mouse-entry switch then redraw the picture.
     */
    public void mouseExited(MouseEvent e) {
        currentSwitch = null;
        if (e.getComponent() instanceof JLabel) delaunayPanel.repaint();
    }

    /**
     * If mouse has been pressed inside the delaunayPanel then add a new site.
     */
    public void mousePressed(MouseEvent e) {
        if (e.getSource() != delaunayPanel) return;
        Pnt point = new Pnt(e.getX(), e.getY());
        if (debug ) System.out.println("Click " + point);
      //  delaunayPanel.addSite(point);
        delaunayPanel.repaint();
    }

    /**
     * Not used, but needed for MouseListener.
     */
    public void mouseReleased(MouseEvent e) {}
    public void mouseClicked(MouseEvent e) {}

    /**
     * @return true iff the "colorful" box is selected
     */
    public boolean isColorful() {
        return colorfulBox.isSelected();
    }

    /**
     * @return true iff doing Voronoi diagram.
     */
    public boolean isVoronoi() {
        return voronoiButton.isSelected();
    }

    /**
     * @return true iff within circle switch
     */
    public boolean showingCircles() {
        return currentSwitch == circleSwitch;
    }

    /**
     * @return true iff within delaunay switch
     */
    public boolean showingDelaunay() {
        return currentSwitch == delaunaySwitch;
    }

    /**
     * @return true iff within voronoi switch
     */
    public boolean showingVoronoi() {
        return currentSwitch == voronoiSwitch;
    }

}

/**
 * Graphics Panel for DelaunayAp.
 */
@SuppressWarnings("serial")
class DelaunayPanel extends JPanel {

    public static Color voronoiColor = Color.magenta;
    public static Color delaunayColor = Color.green;
    public static int pointRadius = 3;

    private DelaunayAp controller;              // Controller for DT
    private Map<Object, Color> colorTable;      // Remembers colors for display
    private Graphics g;                         // Stored graphics context
    private Random random = new Random();       // Source of random numbers

   // private Triangle initialTriangle;           // Initial triangle
    public GraphBuilder gB;
    


    /**
     * Create and initialize the DT.
     */
    public DelaunayPanel (DelaunayAp controller) {
        this.controller = controller;
        colorTable = new HashMap<Object, Color>();
        
        //read custom data
        Obstacles obs = new Obstacles();
        //gB = new GraphBuilder(obs);
	       	        
    }
	
    /**
     * Get the color for the spcified item; generate a new color if necessary.
     * @param item we want the color for this item
     * @return item's color
     */
    private Color getColor (Object item) {
        if (colorTable.containsKey(item)) return colorTable.get(item);
        Color color = new Color(Color.HSBtoRGB(random.nextFloat(), 1.0f, 1.0f));
        colorTable.put(item, color);
        return color;
    }

    /* Basic Drawing Methods */

    /**
     * Draw a point.
     * @param point the Pnt to draw
     */
    public void draw (Pnt point) {
    	point = extraSpace(new Pnt[]{point})[0];
        int r = pointRadius;
        int x = (int) point.coord(0);
        int y = (int) point.coord(1);
        g.fillOval(x-r, y-r, r+r, r+r);
    }

    private Pnt[] extraSpace(Pnt[] points) {
    	for(int i = 0;i< points.length;i++){
    		points[i] = new Pnt(points[i].coord(0)+0.0,points[i].coord(1)+0.0);
    	}
    	return points;
    }

	/**
     * Draw a circle.
     * @param center the center of the circle
     * @param radius the circle's radius
     * @param fillColor null implies no fill
     */
    public void draw (Pnt center, double radius, Color fillColor) {
    	center = extraSpace(new Pnt[]{center})[0];
        int x = (int) center.coord(0);
        int y = (int) center.coord(1);
        int r = (int) radius;
        if (fillColor != null) {
            Color temp = g.getColor();
            g.setColor(fillColor);
            g.fillOval(x-r, y-r, r+r, r+r);
            g.setColor(temp);
        }
        g.drawOval(x-r, y-r, r+r, r+r);
    }

    /**
     * Draw a polygon.
     * @param polygon an array of polygon vertices
     * @param fillColor null implies no fill
     */
    public void draw (Pnt[] polygon, Color fillColor) {
    	polygon = extraSpace(polygon);
        int[] x = new int[polygon.length];
        int[] y = new int[polygon.length];
        for (int i = 0; i < polygon.length; i++) {
            x[i] = (int) polygon[i].coord(0);
            y[i] = (int) polygon[i].coord(1);
        }
        if (fillColor != null) {
            Color temp = g.getColor();
            g.setColor(fillColor);
            g.fillPolygon(x, y, polygon.length);
            g.setColor(temp);
        }
        g.drawPolygon(x, y, polygon.length);
    }

    public void draw (Pnt A, Pnt B, Color fillColor) {
    	Pnt pnts[] = extraSpace(new Pnt[]{A,B});
    	A = pnts[0];
    	B = pnts[1];
        int[] x = new int[2];
        int[] y = new int[2];
        x[0] = (int) A.coord(0);
        y[0] = (int) A.coord(1);
        x[1] = (int) B.coord(0);
        y[1] = (int) B.coord(1);
        if (fillColor != null) {
            Color temp = g.getColor();
            g.setColor(fillColor);
            //g.fillPolygon(x, y, 2);
            g.setColor(temp);
        }
        g.drawLine(x[0], y[0], x[1], y[1]);
    }
    /* Higher Level Drawing Methods */

    /**
     * Handles painting entire contents of DelaunayPanel.
     * Called automatically; requested via call to repaint().
     * @param g the Graphics context
     */
    public void paintComponent (Graphics g) {
        super.paintComponent(g);
        this.g = g;

        // Flood the drawing area with a "background" color
        Color temp = g.getColor();
        if (!controller.isVoronoi()) g.setColor(delaunayColor);
        else if (gB.trans.getDT().contains(DelaunayTriangleTranslator.initialTriangle)) g.setColor(this.getBackground());
        else g.setColor(voronoiColor);
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        g.setColor(temp);

        // If no colors then we can clear the color table
        if (!controller.isColorful()) colorTable.clear();

        // Draw the appropriate picture
        if (controller.isVoronoi())
            drawAllVoronoi(controller.isColorful(), true);
        else drawAllDelaunay(controller.isColorful());

        // Draw any extra info due to the mouse-entry switches
        temp = g.getColor();
        g.setColor(Color.white);
        if (controller.showingCircles()) drawAllCircles();
        if (controller.showingDelaunay()) drawAllDelaunay(false);
        if (controller.showingVoronoi()) drawAllVoronoi(false, false);
        g.setColor(temp);
    }

    /**
     * Draw all the Delaunay triangles.
     * @param withFill true iff drawing Delaunay triangles with fill colors
     */
    public void drawAllDelaunay (boolean withFill) {
        for (Triangle triangle : gB.trans.getDT()) {
            Pnt[] vertices = triangle.toArray(new Pnt[0]);
            draw(vertices, withFill? getColor(triangle) : null);
        }
    }

	/**
     * Draw all the Voronoi cells.
     * @param withFill true iff drawing Voronoi cells with fill colors
     * @param withSites true iff drawing the site for each Voronoi cell
     */
    public void drawAllVoronoi (boolean withFill, boolean withSites) {
        // Keep track of sites done; no drawing for initial triangles sites
    	ArrayList<Line2> lines = gB.createPathLines();
        gB.makeGraph(lines);
        
        
		printGraph(gB.getCoarseGraph());
		printGraph(gB.getFineGraph());
		//printObstacles(gB.getObstacles());

    }
	private void printGraph(PointGraph graph) {
		Pnt centre = new Pnt(200.0,180.0);
		double radius = 85.0;
		draw(centre,radius,null);
		System.out.println("PRINTING GRAPH");

        for(Pnt key : graph.getTheNeighbors().keySet()){
			for(Pnt key2 : graph.getTheNeighbors().get(key).keySet()){
				Edge edge = graph.getTheNeighbors().get(key).get(key2);
                draw(edge.getFirstPoint(),edge.getLastPoint(), null);
                System.out.println(key.coord(0)+"/"+key.coord(1)+" -> "+key2.coord(0)+"/"+key2.coord(1));
        		Pnt[] inter = findCirclePointIntersection(key,key2,centre,radius);
        		if(inter[0] != null){
        			draw(inter[0],10.0,null);
        		}
           		if(inter[1] != null){
        			draw(inter[1],10.0,null);
        		}
                for(Pnt pnt : edge.getPointsOnEdge()){
                	System.out.println("   "+pnt.coord(0)+"/"+pnt.coord(1));
				}
			}
		}		
	}
	
	private Pnt[] findCirclePointIntersection(Pnt linePnt1, Pnt linePnt2, Pnt centre, double radius) {
		Pnt d = linePnt2.subtract(linePnt1); 
		Pnt f = linePnt1.subtract(centre);
		double a = d.dot(d) ;
		double b = 2*f.dot(d) ;
		double c = f.dot(f) - radius*radius ;
		Pnt sol1 = null;
		Pnt sol2;

		double discriminant = b*b-4*a*c;
		if (discriminant < 0){
		  return new Pnt[]{null,null};
		} else {
		  discriminant = Math.sqrt(discriminant);
		  double t1 = (-b + discriminant)/(2*a);
		  double t2 = (-b - discriminant)/(2*a);
		  if( t1 >= 0 && t1 <= 1 ){
			  sol1 = new Pnt(linePnt1.coord(0)+t1*d.coord(0),linePnt1.coord(1)+t1*d.coord(1));
			  sol1 = checkOnLine(linePnt1,linePnt2,sol1);
		  }else{
		    
		  }
		  sol2 = new Pnt(linePnt1.coord(0)+t2*d.coord(0),linePnt1.coord(1)+t2*d.coord(1));
		  sol2 = checkOnLine(linePnt1,linePnt2,sol2);
		}
		return new Pnt[]{sol1,sol2};
	}

	private Pnt checkOnLine(Pnt linePnt1, Pnt linePnt2, Pnt sol) {
		  for(int i = 0; i < 2;i++){
			  if(linePnt1.coord(i) > linePnt2.coord(i)){
				  if(linePnt2.coord(i) > sol.coord(i) || sol.coord(i) > linePnt1.coord(i)){
					  sol = null;
					  break;
				  }
			  } else {
				  if(linePnt2.coord(i) < sol.coord(i) || sol.coord(i) < linePnt1.coord(i)){
					  sol = null;
					  break;
				  }
			  }
		  }
		  return sol;
	}

	private void printObstacles(Obstacles obstacle){
		for(Triangle obs : obstacle.getObstacles()){
            Pnt[] vertices = obs.toArray(new Pnt[0]);
            for(int i = 0; i < vertices.length;i++){
            	int j = i+1;
            	if(i == vertices.length-1){
            		j = 0;
            	}
                draw(vertices[i],vertices[j], null);
            }
		}
	}


    /**
     * Draw all the empty circles (one for each triangle) of the DT.
     */
    public void drawAllCircles () {
        // Loop through all triangles of the DT
        for (Triangle triangle: gB.trans.getDT()) {
            // Skip circles involving the initial-triangle vertices
            if (triangle.containsAny(DelaunayTriangleTranslator.initialTriangle)) continue;
            Pnt c = triangle.getCircumcenter();
            double radius = c.subtract(triangle.get(0)).magnitude();
            draw(c, radius, null);
        }
    }

}
